package cool.taomu.toolkit.git.service

import com.google.inject.Inject
import java.io.File
import java.io.IOException
import java.net.URISyntaxException
import java.util.ArrayList
import java.util.HashMap
import java.util.List
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.ListBranchCommand.ListMode
import org.eclipse.jgit.api.ResetCommand
import org.eclipse.jgit.api.TransportConfigCallback
import org.eclipse.jgit.api.errors.GitAPIException
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.URIish
import org.eclipse.jgit.treewalk.CanonicalTreeParser
import org.slf4j.LoggerFactory
import org.joda.time.DateTime
import cool.taomu.toolkit.utils.inter.ICallback
import cool.taomu.toolkit.git.entity.GitCommitInfoEntity
import cool.taomu.toolkit.git.entity.GitFileHistoryContentEntity
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.treewalk.TreeWalk
import java.nio.charset.StandardCharsets
import org.apache.commons.lang3.StringUtils
import cool.taomu.toolkit.utils.FileTools

class JGitBuilder implements AutoCloseable {
    val static LOG = LoggerFactory.getLogger(JGitBuilder)

    @Inject(optional=true)
    TransportConfigCallback tconfigCallback

    @Inject(optional=true)
    CredentialsProvider credentialsProvider;

    File repoDir;
    Git git;

    new(File repoDir) {
        this.repoDir = repoDir
    }

    def init() throws GitAPIException, IOException {
        LOG.info("初始化仓库:{}", repoDir.absolutePath)
        git = Git.init().setDirectory(repoDir).call();
        return this;
    }

    def open() throws IOException {
        var gitDir = new File(repoDir, ".git");
        LOG.info("打开仓库:{}", gitDir.absolutePath)
        if (!gitDir.exists()) {
            throw new IOException("Not a git repository: " + repoDir.getAbsolutePath());
        }

        var repository = new FileRepositoryBuilder().setGitDir(gitDir).build();
        git = new Git(repository);
        return this;
    }

    def addRemote(String name, String url) throws URISyntaxException, GitAPIException {
        LOG.info("添加远程仓库:{}=>{}", name, url)
        git.remoteAdd().setName(name).setUri(new URIish(url)).call();
        return this;
    }

    def isGitRepoExists(String repoUri, CredentialsProvider credentialsProvider) {
        try {
           var 
            lsRemote = Git.lsRemoteRepository().setRemote(repoUri).setCredentialsProvider(credentialsProvider).
                call();
            // 如果能成功调用 lsRemote 并获取到至少一个 ref，则说明仓库存在
            return lsRemote !== null && !lsRemote.isEmpty();
        } catch (Exception e) {
            System.err.println("仓库不可达或不存在: " + e.getMessage());
            return false;
        }
    }

    def addFile(String filePattern) throws GitAPIException, IOException {
        LOG.info("添加文件:{}", filePattern)
        git.add().addFilepattern(filePattern).call();
        return this;
    }

    def rmFile(String filePattern) {
        LOG.info("删除文件:{}", filePattern)
        git.rm().addFilepattern(filePattern).call();
        return this
    }

    def commit(String message) throws GitAPIException, IOException {
        LOG.info("提交:{}", message)
        git.commit().setMessage(message).call();
        return this;
    }

    def push(String remoteName, String branch) throws GitAPIException, IOException {
        LOG.info("推送分支 {} 到远程 {}", branch, remoteName)
        git.push().setRemote(remoteName).add("refs/heads/" + branch).setTransportConfigCallback(tconfigCallback).
            setCredentialsProvider(credentialsProvider).call();
        return this;
    }

    def cloneFrom(String uri) throws GitAPIException, IOException {
        LOG.info("克隆远程仓库: {} => {}", uri, repoDir.absolutePath)
        git = Git.cloneRepository().setURI(uri).setDirectory(repoDir).setTransportConfigCallback(tconfigCallback).
            setCredentialsProvider(credentialsProvider).call();
        return this;
    }

    /**
     * Fetch 最新提交记录（不合并）
     */
    def fetch() throws GitAPIException, IOException {
        LOG.info("fetch远程仓库")
        git.fetch().setTransportConfigCallback(tconfigCallback).setCredentialsProvider(credentialsProvider).call();
        return this;
    }

    /**
     * 拉取远程分支更新（默认拉取当前分支）
     * 
     */
    def JGitBuilder pull() throws GitAPIException, IOException {
        LOG.info("拉取仓库")
        git.pull().setTransportConfigCallback(tconfigCallback).setCredentialsProvider(credentialsProvider).call();
        return this;
    }

    /**
     * 硬拉取（强制覆盖本地改动）
     */
    def JGitBuilder hardPull() throws GitAPIException, IOException {
        LOG.info("硬拉取远程仓库")
        git.reset().setMode(ResetCommand.ResetType.HARD).call();
        git.pull().setTransportConfigCallback(tconfigCallback).setCredentialsProvider(credentialsProvider).call();
        return this;
    }

    /**
     * 创建并切换到新分支
     */
    def createBranch(String branchName) throws GitAPIException, IOException {
        LOG.info("创建并切换到新分支:{}", branchName)
        git.branchCreate().setName(branchName).call();
        checkout(branchName);
        return this;
    }

    /**
     * 切换分支
     */
    def JGitBuilder checkout(String branchName) throws GitAPIException, IOException {
        LOG.info("切换分支:{}", branchName)
        git.checkout().setName(branchName).call();
        return this;
    }

    /**
     * 删除本地分支
     */
    def JGitBuilder deleteBranch(String branchName) throws GitAPIException, IOException {
        LOG.info("删除本地分支:{}", branchName)
        git.branchDelete().setBranchNames(branchName).setForce(true).call();
        return this;
    }

    /**
     *  打标签并推送到远程
     */
    def tag(String tagName, String message) throws GitAPIException, IOException {
        LOG.info("打标签 {} : {} 并推送到远程:", tagName, message)
        git.tag().setName(tagName).setMessage(message).call();
        git.push().add("refs/tags/" + tagName).setTransportConfigCallback(tconfigCallback).
            setCredentialsProvider(credentialsProvider).call();
        return this;
    }

    /**
     * 获取当前分支名称
     */
    def String getCurrentBranch() throws IOException {
        LOG.info("获取当前分支名称")
        return git.getRepository().getBranch();
    }

    /**
     * 获取本地所有分支
     */
    def List<String> listBranches(ListMode mode) throws IOException {
        LOG.info("获取所有分支")
        var branches = git.branchList().setListMode(mode).call();
        var List<String> branchNames = new ArrayList<String>();
        for (Ref ref : branches) {
            branchNames.add(ref.getName());
        }
        return branchNames;
    }

    /**
     * 获取远程所有分支
     */
    def List<String> listRemoteBranches() throws IOException,GitAPIException {
        LOG.info("获取远程所有分支")
        var branches = git.lsRemote().setHeads(true).setTransportConfigCallback(tconfigCallback).
            setCredentialsProvider(credentialsProvider).call();
        var branchNames = new ArrayList<String>();
        for (Ref ref : branches) {
            branchNames.add(ref.getName());
        }
        return branchNames;
    }

    /**
     *  获取提交历史（最多 maxCount 条）
     */
    def getCommitHistory(int maxCount) throws GitAPIException, IOException {
        LOG.info("获取提交历史（最多 maxCount 条）")
        return git.log().setMaxCount(maxCount).call();
    }

    /**
     *  获取两个分支之间的差异
     */
    def List<DiffEntry> diffBetween(String branch1, String branch2) throws GitAPIException, IOException {
        LOG.info(" 获取两个分支之间的差异: {} => {}", branch1, branch2)
        try (var RevWalk revWalk = new RevWalk(git.getRepository())) {
            var oldHead = git.getRepository().resolve(branch1);
            var newHead = git.getRepository().resolve(branch2);

            if (oldHead === null || newHead === null) {
                throw new IOException("One of the branches not found: " + branch1 + ", " + branch2);
            }

            var oldCommit = revWalk.parseCommit(oldHead);
            var newCommit = revWalk.parseCommit(newHead);

            var oldTreeIter = new CanonicalTreeParser();
            try (var oreader = git.getRepository().newObjectReader()) {
                oldTreeIter.reset(oreader, oldCommit.getTree());
            }

            var newTreeIter = new CanonicalTreeParser();
            try (var nreader = git.getRepository().newObjectReader()) {
                newTreeIter.reset(nreader, newCommit.getTree());
            }
            return git.diff().setOldTree(oldTreeIter).setNewTree(newTreeIter).call();
        }
    }

    /**
     * 获取指定文件的历史提交记录
     */
    def getFileHistory(String filePath) throws GitAPIException, IOException {
        LOG.info("获取指定文件的历史提交记录")
        return git.log().addPath(filePath).call();
    }

    /**
     * 获取当前 HEAD 的 commit hash
     */
    def String getCurrentCommitHash() throws IOException {
        LOG.info("获取当前 HEAD 的 commit hash")
        return git.getRepository().resolve("HEAD").getName();
    }

    def status(String remoteRepository) {
        var map = new HashMap<String, String>();
        var status = git.status.call();
        map.put("Added", status.added.toString());
        map.put("Changed", status.changed.toString());
        map.put("Conflicting", status.conflicting.toString());
        map.put("ConflictingStageState", status.conflictingStageState.toString());
        map.put("IgnoredNotInIndex", status.ignoredNotInIndex.toString());
        map.put("Missing", status.missing.toString());
        map.put("Modified", status.modified.toString());
        map.put("Removed", status.removed.toString());
        map.put("UntrackedFiles", status.untracked.toString());
        map.put("UntrackedFolders", status.untrackedFolders.toString());
        return map;
    }

    def Git getGit() {
        LOG.info("获取Git对象")
        return git;
    }

    override void close() {
        LOG.info("关闭Git")
        if (git !== null) {
            git.close();
        }
    }

    // 以下是扩展内容
    /**
     * 获取commit历史
     */
    def getCommitIds(Integer num, ICallback<GitCommitInfoEntity> call) {
        LOG.info("获取commit历史");
        val RevWalk walk = new RevWalk(git.getRepository());
        var head = walk.parseCommit(git.getRepository().resolve("HEAD"));
        walk.reset();
        walk.markStart(head);
        var walkList = walk.toList
        LOG.info("获取到{}条commit id", walkList.size());
        if (num !== null && call !== null) {
            var size = Math.min(num, walkList.size());
            walkList.subList(0, size).forEach [
                var entity = new GitCommitInfoEntity();
                entity.id = it.getName;
                entity.time = new DateTime(it.committerIdent.when);
                LOG.info("commit id:{}", entity.toString());
                call.call(entity);
            ]
        } else if (call !== null) {
            walkList.forEach [
                var entity = new GitCommitInfoEntity();
                entity.id = it.getName;
                entity.time = new DateTime(it.committerIdent.when);
                LOG.info("commit id:{}", id);
                call.call(entity);
            ]
        }
        walk.dispose
    }

    /**
     * 获取文件內容
     */
    def retrieveFileContent(String filePath, List<String> commitIds, ICallback<GitFileHistoryContentEntity> call) {
        LOG.info("获取文件內容");
        val RevWalk walk = new RevWalk(git.getRepository());
        if (StringUtils.isNotBlank(filePath)) {
            commitIds.forEach [ commitId |
                var RevCommit commit = walk.parseCommit(git.getRepository().resolve(commitId));
                var fpath = filePath.replace(repoDir.absolutePath + File.separator, "").replace(File.separator, "/");
                handleCommit(git, commitId, commit, fpath, walk, call);
            ]
        } else {
            FileTools.scannerFile(repoDir.absolutePath, [
                if (it.file && !it.absolutePath.contains(".git")) {
                    commitIds.forEach [ commitId |
                        var RevCommit commit = walk.parseCommit(git.getRepository().resolve(commitId));
                        var fpath = it.absolutePath.replace(repoDir.absolutePath + File.separator, "").replace(
                            File.separator, "/");
                        handleCommit(git, commitId, commit, fpath, walk, call);
                    ]
                }
            ])
        }
        walk.dispose;
    }

    private def void handleCommit(Git git, String commitId, RevCommit commit, String filePath, RevWalk walk,
        ICallback<GitFileHistoryContentEntity> call) {
        try (var TreeWalk treeWalk = TreeWalk.forPath(git.getRepository(), filePath, commit.getTree())) {
            if (treeWalk !== null) {
                var objectId = treeWalk.getObjectId(0); // 文件在第一个索引位置  
                var loader = git.getRepository().open(objectId);
                var byte[] data = loader.getBytes();
                var String content = new String(data, StandardCharsets.UTF_8);
                LOG.info("从{}提交中获取{}文件内容", commit.getName(), filePath);
                var gfhc = new GitFileHistoryContentEntity();
                gfhc.filePath = filePath;
                gfhc.content = content;
                gfhc.time = new DateTime(commit.committerIdent.when);
                gfhc.commitId = commitId;
                call.call(gfhc);
            } else {
                LOG.info("从{}提交没有发现文件{},提取结束", commit.getName(), filePath);
            }
        }
    }
}
